package emissionsviewer;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import org.jdesktop.application.Application;
import org.jdesktop.application.SingleFrameApplication;
import java.util.HashMap;
import java.awt.Point;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;

/**
 * The main class of the application.
 */
public class EmissionsViewerApp extends SingleFrameApplication {

    /**
     * At startup create and show the main frame of the application.
     */
    @Override
    protected void startup() {
        show(new EmissionsViewerView(this));
    }

    /**
     * This method is to initialize the specified window by injecting resources.
     * Windows shown in our application come fully initialized from the GUI
     * builder, so this additional configuration is not needed.
     */
    @Override
    protected void configureWindow(java.awt.Window root) {
    }

    /**
     * A convenient static getter for the application instance.
     * @return the instance of EmissionsViewerApp
     */
    public static EmissionsViewerApp getApplication() {
        return Application.getInstance(EmissionsViewerApp.class);
    }

    /**
     * Main method launching the application.
     */
    public static void main(String[] args) {
        launch(EmissionsViewerApp.class, args);
    }

    /**
     * 
     * @param key
     */
    public void addNode(int key, Double x, Double y) {
        Point p = new Point(x.intValue(), y.intValue());
        nodes.put(key, p);
    }

    public boolean checkNodes() {
        return !(nodes.isEmpty());
    }

    /**
     * Returns the largest value such that we can size the image
     * @param which false = I, true = J
     * @param key
     */
    protected double[] getRange() {
        HashMap hm;

        double[] range = new double[4]; // {min_x, max_x, min_y, max_y}


        boolean start = true;
        Point P;
        Iterator it = nodes.values().iterator();
        while (it.hasNext()) {
            P = (Point) it.next();

            if (start) {
                // Initialize
                range[0] = P.getX();
                range[1] = P.getX();
                range[2] = P.getY();
                range[3] = P.getY();
                start = false;
            }

            range[0] = Math.min(range[0], P.getX());
            range[1] = Math.max(range[1], P.getX());

            range[2] = Math.min(range[2], P.getY());
            range[3] = Math.max(range[3], P.getY());
        }

        return range;
    }

    /**
     * Setter for the data file
     * @param file data file
     */
    public void setDataFile(File file) {
        dataFile = file;
    }

    public File getDataFile() {
        //return dataFile;
        return new File("/home/matt/gaea-matt/karman/data.out");
    }

    public void setRefFile(File refFile) {
        this.refFile = refFile;
    }

    public File getRefFile() {
        //return refFile;
        return new File("/home/matt/gaea-matt/karman/ref.csv");
    }

    public void setEmitType(int type) {
        chosenEmitType = type;
    }

    public int getEmitType() {
        return chosenEmitType;
    }

    public void setLinkTypes(ArrayList lts) {
        this.showLinkTypes = lts;
    }
    public ArrayList getLinkTypes() {
        if (this.showLinkTypes == null) {
             return new ArrayList();
        } else {
            return this.showLinkTypes;
        }
    }

    public int[] getLineRange() {
        // This should be set in the UI
        int[] range = new int[2];

        range[0] = 0;
        range[1] = 19000;
        return range;
    }

    public void clearNodes() {
        this.nodes.clear();
    }

    public void loadNodes() {
        File file = getRefFile();

        // Clear the current map
        clearNodes();

        try {
            BufferedReader bufRead = new BufferedReader(new FileReader(file));
            int row = 0;
            int col = 0;
            String line = null;
            double[] vals = new double[6];
            Double keyI, keyJ;

            while ((line = bufRead.readLine()) != null) {
                //System.out.println("line: " + line);
                StringTokenizer st = new StringTokenizer(line, ",");
                while (st.hasMoreTokens()) {
                    vals[col] = Double.parseDouble(st.nextToken());
                    col++;
                    if (col >= 5) {
                        break;
                    }
                }
                //System.out.println("Row:" + col + " AddNode(" + vals[0] + ") = (" + vals[2] + ", " + vals[3] + ")");
                keyI = new Double(vals[0]);
                keyJ = new Double(vals[1]);
                addNode(keyI.intValue(), vals[2], vals[3]);
                addNode(keyJ.intValue(), vals[4], vals[5]);

                row++;
                col = 0;
            }
        } catch (IOException ex) {
            System.out.println("Problem accessing file" + file.getAbsolutePath());
        }
    }

    protected ElinkData loadData(int cmt, int[] lineRange) {

        int row = 0;
        int col = 0;
        String line = null;

        int ni = 0;
        int nj = 0;
        int linkType = 0;
        double emitAmount = 0;
        Number num;
        NumberFormat f = new SciNotationFormat("0.###E0");
        String nums = "";

        // Get the list of link types we're accepting
        ArrayList lts = getLinkTypes();

        int eRange = lineRange[1] - lineRange[0];

        // Run through values to get some stats, and store the values.
        // Probably easier to take up the memory in recording these values than
        // to start run  a string tokenizer on them twice.
        double e_min, e_max, e_mean;
        int e_count = 0;
        e_min = 0;
        e_max = 0;
        e_mean = 0;
        ArrayList<Elink> data = new ArrayList<Elink>(lineRange[1] - lineRange[0]);
        row = 0;
        col = 0;

        try {
            BufferedReader bufRead = new BufferedReader(new FileReader(this.getDataFile()));

            while ((line = bufRead.readLine()) != null) {
                // Make sure we're reading proper values.
                if (row < lineRange[0]) {
                    continue; // Skip
                } else if (row > lineRange[1]) {
                    break; // We're done
                }

                // Start parsing the vlaues.
                StringTokenizer st = new StringTokenizer(line, " ");
                col = 0;
                while (st.hasMoreTokens()) {
                    try {
                        nums = st.nextToken();
                        num = f.parse(nums);
                    } catch (ParseException ex) {
                        System.out.println("Count not parse: " + nums);
                        break;
                    }

                    if (col == 0) {
                        ni = num.intValue();
//System.out.println("Parsed '" + nums + " as " + ni);
                    } else if (col == 1) {
                        nj = num.intValue();
//System.out.println("Parsed '" + nums + " as " + nj);
                    } else if (col == 2) {
                        linkType = num.intValue();

                        if (!lts.contains(linkType))
                             continue; // Skipping this type
                    } else if (col == cmt) {
                        emitAmount = num.doubleValue();
                    }

                    col++;
                    if (col >= 24) // protect..
                    {
                        break;
                    }
                }
                // Read in a line, now store it
                data.add(new Elink(ni, nj, linkType, emitAmount));
                e_count++;

                e_min = Math.min(e_min, emitAmount);
                e_max = Math.max(e_max, emitAmount);
                e_mean = e_mean + (1 / ((double) e_count + 1.0)) * (emitAmount - e_mean);

                // Increment the row count
                row++;
            }
            System.out.println("Data [should] has " + e_count + " elements, with an average value of " + Math.round(e_mean));

        } catch (IOException ex) {
            System.out.println("Problem accessing file" + this.getDataFile().getAbsolutePath());
        }
        return new ElinkData(data, e_min, e_max, e_mean, e_count);
    }

    public BufferedImage generatePlot() {
        int row = 0;
        Double thickness;
        int ni = 0;
        int nj = 0;
        int linkType = 0;
        Point origin, dest;
        double emitAmount = 0;
        int notFoundCount = 0;

        // These should be put into the GUI
        double xscale = 0.01; // Multiplier
        double yscale = 0.0015; // Multiplier


        // Load nodes
        loadNodes();


        double[] range = getRange();
        System.out.println("X range: [" + range[0] + ", " + range[1] + "] = " + (range[1] - range[0]) + ", " +
                "Y range: [" + range[2] + ", " + range[3] + "] = " + (range[1] - range[0]) + "");
        Double width = new Double((range[1] - range[0]) * xscale);
        Double height = new Double((range[3] - range[2]) * yscale);
        System.out.println("Attempting to generate plot of size (" + Math.round(width) + ", " + Math.round(height) + ")");
        BufferedImage plot = new BufferedImage(width.intValue() + 1, height.intValue() + 1, BufferedImage.TYPE_INT_RGB);

        int cmt = getEmitType() + 3; // Magic number 3 is the column offset!!

        // Create a graphics context on the buffered image
        Graphics2D g2 = plot.createGraphics();
        // Draw a white background
        g2.setColor(Color.white);
        g2.fillRect(0, 0, plot.getWidth(), plot.getHeight());

        int[] lineRange = getLineRange();
        int eRange = lineRange[1] - lineRange[0];

        //
        // Load all the data...
        double e_min, e_max, e_mean;
        int e_count;
        ArrayList<Elink> data;
        ElinkData eld = loadData(cmt, lineRange);

        data = eld.getElinks();
        e_min = eld.min;
        e_max = eld.max;
        e_mean = eld.mean;
        e_count = eld.count;
        // Data and stats on data loaded
        //

        // Iterate through the data and draw it
        Iterator it = data.iterator();
        row = 0;
        Elink el;
        while (it.hasNext()) {
            row++;

            if (row > 19000) {
                System.out.println("Breaking at the first 19k lines, this should be made an GUI option!");
                break;
            }

            // Load the element
            el = (Elink) it.next();
            ni = el.ni;
            nj = el.nj;
            linkType = el.linkType;
            emitAmount = el.mag;

            // Find the points on the plot
            if (nodes.containsKey(ni)) {
                origin = nodes.get(ni);
            } else {
                System.out.println("Could not find ni = " + ni);
                notFoundCount++;
                continue;
            }

            if (nodes.containsKey(nj)) {
                dest = nodes.get(nj);
            } else {
                System.out.println("Could not find nj = " + nj);
                notFoundCount++;
                continue;
            }

            // Use the stats to calculate what the scale should be.
            // This should also be overridable in a UI option such that it
            // can be held for multible runs (for comparison)
            thickness = Math.max((emitAmount - e_min) / ((double) eRange) * 50, 1); // Normalize it and put it on a 0-10 scale.

            // Draw the line
            drawELine(g2, origin, dest, linkType, range[0], range[2], xscale, yscale, thickness.intValue());

        } // End loop through lines

        System.out.println("Could not find " + notFoundCount + " nodes.");

        return plot;
    }

    public void drawELine(Graphics2D g, Point origin, Point dest, int linkType, double min_x, double min_y, double xscale, double yscale, int t) {
        double xo, yo, xd, yd;

        // Origin
        xo = (origin.getX() - min_x) * xscale;
        yo = (origin.getY() - min_y) * yscale;

        // Dest
        xd = (dest.getX() - min_x) * xscale;
        yd = (dest.getY() - min_y) * yscale;

        // Draw link
        g.setStroke(new BasicStroke(t));
        // Replace with a hashmap or something later
        switch (linkType) {
            case 1:
                g.setColor(Color.DARK_GRAY);
                break;
            case 2:
                g.setColor(Color.BLUE);
                break;
            case 6:
                g.setColor(Color.RED);
                break;
            default:
                g.setColor(Color.BLACK);
        }
        g.draw(new Line2D.Double(xo, yo, xd, yd));

        //               System.out.println("Drawing line form (" +
        //                       Math.round(xo) + ", " + Math.round(yo) + ") to (" +
        //                       Math.round(xd) + ", " + Math.round(yd) + ") with weight: " + thickness);
    }

    // basically a structure
    protected class Elink {

        public int ni, nj, linkType;
        public double mag;

        public Elink(int ni, int nj, int linkType, double mag) {
            this.ni = ni;
            this.nj = nj;
            this.linkType = linkType;
            this.mag = mag;
        }
    }

    protected class ElinkData {

        public ArrayList Elinks;
        public double min;
        public double max;
        public int count;
        public double mean;

        public ElinkData(ArrayList Elinks, double min, double max, double mean, int count) {
            this.Elinks = Elinks;
            this.min = min;
            this.max = max;
            this.mean = mean;
            this.count = count;
        }
        public ArrayList<Elink> getElinks() {
            return this.Elinks;
        }
    }
    private HashMap<Integer, Point> nodes = new HashMap<Integer, Point>();
    private File dataFile, refFile;
    protected int chosenEmitType;
    protected ArrayList showLinkTypes;
}
